﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Licenta1.RayTracer
{
    public abstract class MathShape
    {
        abstract public double[] Intersects(Ray ray);
        abstract public Vector GetNormal(Vector surfacePoint);
        abstract public bool IsInside(Vector point);
        abstract public bool IsOnSurface(Vector point);
    }

    public class MathSphere : MathShape
    {
        Vector center;
        double radius;

        public MathSphere(Vector center, double radius)
        {
            this.center = center;
            this.radius = radius;
        }

        public override double[] Intersects(Ray ray)
        {
            Vector v = ray.point - center;
            Vector d = ray.direction;
            double r = radius;


            double sum = (v * d) * (v * d) - (v * v - r * r);
            if (sum < 0)
                return new double[0];

            double first = -(v * d) + Math.Sqrt(sum);
            double second = -(v * d) - Math.Sqrt(sum);
            
            if (first < 0)
            {
                if (second < 0)
                    return new double[0];
                else
                    return new double[1] { second };
            }
            else 
            {
                if (second < 0)
                    return new double[] { first };
                else
                    return new double[2] { first, second };
            }
        }

        public override Vector GetNormal(Vector surfacePoint)
        {
            return (surfacePoint - center).Normalize();
        }

        public override bool IsInside(Vector point)
        {
            return (point - center).Length() <= radius + 10E-13;
        }

        public override bool IsOnSurface(Vector point)
        {
            return Math.Abs((point - center).Length() - radius) < 10e-13;
        }
    }

    public class MathPlane : MathShape
    {
        double A, B, C, D;

        public MathPlane(double A, double B, double C, double D)
        {
            this.A = A;
            this.B = B;
            this.C = C;
            this.D = D;
        }

        public override double[] Intersects(Ray ray)
        {
            Vector Pn = new Vector(A, B, C).Normalize();
            Vector R0 = ray.point;
            Vector Rd = ray.direction;
            double t = 0;
            double Vd = Pn * Rd;

            if (Vd == 0)
                return new double[0];
            else
            {
                t = -(Pn * R0 + D) * (1 / Vd);
                if (t < 0)
                    return new double[0];
                else
                    return new double[1] { t };
            }
        }

        public override Vector GetNormal(Vector surfacePoint)
        {
            return new Vector(A, B, C).Normalize();
        }

        public override bool IsInside(Vector point)
        {
            return false;
        }

        public override bool IsOnSurface(Vector point)
        {
            return Math.Abs(A * point.x + B * point.y + C * point.z + D) < 10e-14;
        }
    }

    public class MathCube : MathShape
    {
        MathPlane p1, p2, p3, p4, p5, p6;
        Vector center;
        double length;

        public MathCube(Vector center, double length)
        {
            this.center = center;
            this.length = length / 2;

            p1 = new MathPlane(0, 0, -1, center.z + length / 2);
            p6 = new MathPlane(0, 0, -1, center.z + -length / 2);
            p2 = new MathPlane(0, -1, 0, center.y + length / 2);
            p5 = new MathPlane(0, -1, 0, center.y + -length / 2);
            p3 = new MathPlane(-1, 0, 0, center.x + length / 2);
            p4 = new MathPlane(-1, 0, 0, center.x + -length / 2);
        }

        public override bool IsInside(Vector point)
        {
            if (point.x <= (center.x + length) &&
                point.x >= (center.x - length) &&
                point.y <= (center.y + length) &&
                point.y >= (center.y - length) &&
                point.z <= (center.z + length) &&
                point.z >= (center.z - length))
                return true;

            return false;
        }

        public override double[] Intersects(Ray ray)
        {
            List<double> final_intersections = new List<double>();

            foreach (double i in p1.Intersects(ray).Union(p6.Intersects(ray)))
            {
                Vector point = ray.point + ray.direction * i;

                if (isBetween(point.y, center.y - length, center.y + length) &&
                    isBetween(point.x, center.x - length, center.x + length))
                    final_intersections.Add(i);
            }
            foreach (double i in p2.Intersects(ray).Union(p5.Intersects(ray)))
            {
                Vector point = ray.point + ray.direction * i;

                if (isBetween(point.z, center.z - length, center.z + length) &&
                    isBetween(point.x, center.x - length, center.x + length))
                    final_intersections.Add(i);
            }
            foreach (double i in p3.Intersects(ray).Union(p4.Intersects(ray)))
            {
                Vector point = ray.point + ray.direction * i;

                if (isBetween(point.y, center.y - length, center.y + length) &&
                    isBetween(point.z, center.z - length, center.z + length))
                    final_intersections.Add(i);
            }

            return final_intersections.ToArray();
        }

        private bool isBetween(double p, double p_2, double p_3)
        {
            return (p >= p_2) && (p <= p_3); 
        }

        public override Vector GetNormal(Vector surfacePoint)
        {
            if (p1.IsOnSurface(surfacePoint))
                return p1.GetNormal(surfacePoint);
            else if (p2.IsOnSurface(surfacePoint))
                return p2.GetNormal(surfacePoint);
            else if (p3.IsOnSurface(surfacePoint))
                return p3.GetNormal(surfacePoint);
            else if (p4.IsOnSurface(surfacePoint))
                return p4.GetNormal(surfacePoint);
            else if (p5.IsOnSurface(surfacePoint))
                return p5.GetNormal(surfacePoint);
            else if (p6.IsOnSurface(surfacePoint))
                return p6.GetNormal(surfacePoint);
            else
                throw new Exception("Exception: get normale cube failed.");
        }

        public override bool IsOnSurface(Vector point)
        {
            if (isBetween(point.y, center.y - length - 10e-10, center.y + length + 10e-10) &&
                isBetween(point.x, center.x - length - 10e-10, center.x + length + 10e-10) &&
                (p1.IsOnSurface(point) || p6.IsOnSurface(point)))
                return true;
            else if (isBetween(point.z, center.z - length - 10e-10, center.z + length + 10e-10) &&
                     isBetween(point.x, center.x - length - 10e-10, center.x + length + 10e-10) &&
                     (p2.IsOnSurface(point) || p5.IsOnSurface(point)))
                return true;
            else if (isBetween(point.y, center.y - length - 10e-10, center.y + length + 10e-10) &&
                     isBetween(point.z, center.z - length - 10e-10, center.z + length + 10e-10) &&
                     (p3.IsOnSurface(point) || p4.IsOnSurface(point)))
                return true;
            else
                return false;
        }
    }

    //class MathCylinder : MathShape
    //{
    //    double x, y, a, b;

    //    public MathCylinder(double x, double y, double a, double b)
    //    {
    //        this.x = x;
    //        this.y = y;
    //        this.a = a;
    //        this.b = b;
    //    }


    //    public override double[] Intersects(Ray ray)
    //    {
    //        return new double[0];
    //    }
    //}
}
